﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

public class HtmlAttribute
{
    public string AttributeName { get; set; }
    public string AttributeValue { get; set; }

    public static HtmlAttribute @render(string attributeName, string attributeValue)
    {
        return new HtmlAttribute { AttributeName = attributeName, AttributeValue = attributeValue };
    }

    public override string ToString()
    {
        return string.Format("{0}=\"{1}\"", AttributeName, AttributeValue);
    }
}

public class HtmlStyle : HtmlAttribute
{
    public StyleAttribute[] StyleProperties { get; set; }

    public static HtmlStyle Style(params StyleAttribute[] styleProperties)
    {
        return new HtmlStyle { AttributeName = "style", StyleProperties = styleProperties };
    }

    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();
        Array.ForEach(StyleProperties, (Action<StyleAttribute>)(x =>
        {
            builder.AppendFormat("{0};", x);
        }));
        return string.Format("{0}=\"{1}\"", AttributeName, builder.Remove(builder.Length - 1, 1));
    }
}

public class StyleAttribute : HtmlAttribute
{
    public static StyleAttribute Prop(string attributeName, string attributeValue)
    {
        return new StyleAttribute { AttributeName = attributeName, AttributeValue = attributeValue };
    }

    public override string ToString()
    {
        return string.Format("{0}:{1}", AttributeName, AttributeValue);
    }
}

public class HtmlHelper : IDisposable
{
    const string Content = "";
    const char IndentationPattern = '\t';
    private StringBuilder _html;
    private readonly HashSet<string> _idCollection;
    private int _indent = 0;
    private Queue<HtmlAttribute> _queueAttribute = new Queue<HtmlAttribute>();

    public HtmlHelper()
    {
        _html = new StringBuilder();
        _idCollection = new HashSet<string>();
    }

    #region Write Html
    
    public void Write(string tagName)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("<{0}", tagName);
        RenderAttributes();
        _html.Append("/>");
    }

    public void Write(HtmlTag tag)
    {
        string tagName = tag.GetStringValue();
        Write(tagName);
    }

    public void Write(string tagName, string content)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("<{0}", tagName);
        RenderAttributes();
        _html.Append(">");
        _html.Append(content);
        _html.AppendFormat("</{0}>", tagName);
    }

    public void Write(HtmlTag tag, object content)
    {
        string tagName = tag.GetStringValue();
        Write(tag, content.ToString());
    }

    public void Write(HtmlTag tag, string content)
    {
        string tagName = tag.GetStringValue();
        Write(tagName, content);
    }
    #endregion

    #region Write Line
    public void WriteLine()
    {
        _html.AppendLine();
    }

    public void WriteLine(string line)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendLine(line);
    }

    public void WriteLine(HtmlTag tag)
    {
        Write(tag.GetStringValue());
    }

    public void WriteLine(string tagName, string content)
    {
        Write(tagName, content);
        _html.AppendLine();
    }

    public void WriteLine(HtmlTag tag, string content)
    {
        WriteLine(tag.GetStringValue(), content);
    }
    #endregion

    #region Write Tag

    public IDisposable @div(HtmlTag tag)
    {
        return RenderTag(HtmlTag.Div);
    }

    public IDisposable RenderTag(HtmlTag tag)
    {
        string tagName = tag.GetStringValue();
        return RenderTag(tagName);
    }

    public IDisposable RenderTag(string tagName)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("<{0}", tagName);
        RenderAttributes();
        _html.AppendLine(">");
        Indent();
        return EndAction(() =>
        {
            UnIndent();
            _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
            _html.AppendFormat("</{0}>", tagName);
            _html.AppendLine();
        });
    }

    public IDisposable WriteTag(HtmlTag tag)
    {
        string tagName = tag.GetStringValue();
        return WriteTag(tagName);
    }

    public IDisposable WriteTag(string tagName)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("<{0}", tagName);
        RenderAttributes();
        _html.AppendLine(">");
        Indent();
        return EndAction(() =>
        {
            UnIndent();
            _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
            _html.AppendFormat("</{0}>", tagName);
            _html.AppendLine();
        });
    }

    #endregion

    #region Aditional Write (Script, Attributes)
    public IDisposable WriteScriptFunction(string functionName, params string[] parameters)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("function {0}(", functionName);
        _html.Append(string.Join(",", parameters));
        _html.AppendLine(") {");
        Indent();
        return EndAction(() =>
        {
            UnIndent();
            _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
            _html.Append("}");
            _html.AppendLine();
        });
    }

    public IDisposable WriteScriptBlock(string blockDeclaration)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("{0} = {{", blockDeclaration);
        _html.AppendLine();
        Indent();
        return EndAction(() =>
        {
            UnIndent();
            _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
            _html.Append("};");
            _html.AppendLine();
        });
    }

    public IDisposable WriteScriptArray(string blockDeclaration)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.AppendFormat("{0} = [", blockDeclaration);
        _html.AppendLine();
        Indent();
        return EndAction(() =>
        {
            UnIndent();
            _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
            _html.Append("];");
            _html.AppendLine();
        });
    }

    public void WriteScriptLine(string line)
    {
        _html.Append(string.Empty.PadRight(_indent, IndentationPattern));
        _html.Append(line.TrimEnd(';'));
        _html.AppendLine(";");
    }

    public void AddAttribute(HtmlAttribute attribute)
    {
        _queueAttribute.Enqueue(attribute);
    }

    public void AddAttribute(string name, string value)
    {
        AddAttribute(HtmlAttribute.render(name, value));
    }

    public void AddId(string value)
    {
        AddAttribute(HtmlAttribute.render("id", value));
    }

    public void AddClass(string value)
    {
        AddAttribute(HtmlAttribute.render("class", value));
    }

    public void AddStyle(params string[] value)
    {
        AddAttribute(HtmlAttribute.render("style", string.Join(";", value)));
    }

    private void RenderAttributes()
    {
        if (_queueAttribute.Count > 0)
        {
            do
            {
                var item = _queueAttribute.Dequeue();
                if (string.Compare(item.AttributeName, "id", true) == 0)
                {
                    if (_idCollection.Contains(item.AttributeValue))
                        throw new ConflictException();
                    else
                        _idCollection.Add(item.AttributeValue);
                }

                _html.AppendFormat(" {0}", item);
            }
            while (_queueAttribute.Count > 0);
        }
    }

    #endregion

    private void Indent()
    {
        _indent++;
    }

    private void UnIndent()
    {
        _indent--;
    }

    public override string ToString()
    {
        return _html.ToString();
    }

    private IDisposable EndAction(Action end)
    {
        return new NestedInstruction(end);
    }

    protected class NestedInstruction : IDisposable
    {
        private readonly Action endAction;

        public NestedInstruction(Action end)
        {
            endAction = end;
        }

        public void Dispose()
        {
            endAction();
        }
    }

    public void Dispose()
    {
        _html = null;
    }
}

public enum HtmlTag
{
    [StringValue("br")]
    Br,
    [StringValue("h1")]
    H1,
    [StringValue("h2")]
    H2,
    [StringValue("h3")]
    H3,
    [StringValue("p")]
    P,
    [StringValue("div")]
    Div,
    [StringValue("table")]
    Table,
    [StringValue("th")]
    Th,
    [StringValue("tr")]
    Tr,
    [StringValue("td")]
    Td,
    [StringValue("span")]
    Span,
    [StringValue("a")]
    A,
    [StringValue("img")]
    Img,
    [StringValue("script")]
    Script,
    [StringValue("ul")]
    Ul,
    [StringValue("li")]
    Li
}

[Serializable]
public class ConflictException : Exception
{
    public ConflictException()
        : base("Conflict Exception") { }
}